﻿using System;
using System.Threading.Tasks;
using CkSoftware.GroupMe.Sdk.Bot.Commands;
using CkSoftware.GroupMe.Sdk.Bot.Dao;
using CkSoftware.GroupMe.Sdk.Core;
using CkSoftware.GroupMe.Sdk.Core.Dao;
using CkSoftware.GroupMe.Sdk.Core.Exceptions;

namespace CkSoftware.GroupMe.Sdk.Bot
{
	/// <summary>
	/// Management class for creating, starting and stopping a bot and for sending messages through a running bot.
	/// </summary>
	public class GroupMeBot
	{
		private readonly CreateBotCommand _createBotCommand;
		private readonly IWebApiAccess _webApiAccess;
		private BotData _currentBotInstance;

		/// <summary>
		/// Instantiates a new <see cref="GroupMeBot" /> class and creates a new bot with the given data.
		/// </summary>
		/// <param name="webApiAccess">The <see cref="IWebApiAccess" /> to access the GroupMe API.</param>
		/// <param name="name">The name of the new bot to craete.</param>
		/// <param name="groupId">The group id where the new bot should live in.</param>
		/// <exception cref="ArgumentNullException">Occurs if the webApiAccess is null.</exception>
		public GroupMeBot(IWebApiAccess webApiAccess, string name, string groupId)
		{
			if (webApiAccess == null)
			{
				throw new ArgumentNullException("webApiAccess");
			}

			_webApiAccess = webApiAccess;

			_createBotCommand = new CreateBotCommand(name, groupId);
		}

		/// <summary>
		/// Instantiate a new <see cref="GroupMeBot" /> class with the given bot-data.
		/// </summary>
		/// <param name="webApiAccess">The <see cref="IWebApiAccess" /> to access the GroupMe API.</param>
		/// <param name="botData">The bot data to use for already created bots.</param>
		/// <exception cref="ArgumentNullException">Occurs if either the webApiAccess or the botData is null.</exception>
		public GroupMeBot(IWebApiAccess webApiAccess, BotData botData)
		{
			if (webApiAccess == null)
			{
				throw new ArgumentNullException("webApiAccess");
			}

			if (botData == null)
			{
				throw new ArgumentNullException("botData");
			}

			_webApiAccess = webApiAccess;
			_currentBotInstance = botData;
		}

		/// <summary>
		/// The currently assigned bot-data object, containing all GroupMe bot relevant data.
		/// </summary>
		public BotData Data
		{
			get { return _currentBotInstance; }
		}

		/// <summary>
		/// Destroys the currently assigned bot. The bot will no longer be available on GroupMe and needs to be recreated either
		/// using the GroupMe UI or using the StartBot command.
		/// </summary>
		public void DestroyBot()
		{
			Task destroyBotTask = DestroyBotAsyncInternal();
			Task.WaitAll(destroyBotTask);
		}

		/// <summary>
		/// Destroys the currently assigned bot asynchronous. The bot will no longer be available on GroupMe and needs to be
		/// recreated either using the GroupMe UI or using the StartBotAsync command.
		/// </summary>
		/// <returns>The awaitable task.</returns>
		public async Task DestroyBotAsync()
		{
			await DestroyBotAsyncInternal();
		}

		/// <summary>
		/// Creates a new <see cref="GroupMeBotSendMessage" />, ready to be sent on behalf of this bot. To send the message, call
		/// the Submit() or SubmitAsync() method.
		/// </summary>
		/// <param name="message">The message text to send.</param>
		/// <returns>The correctly contructed <see cref="GroupMeBotSendMessage" /> object.</returns>
		public GroupMeBotSendMessage SendMessage(string message)
		{
			return new GroupMeBotSendMessage(this, message);
		}

		/// <summary>
		/// Starts the bot using the currently assigned bot-data.
		/// </summary>
		/// <exception cref="NotSupportedException">Occurs if the bot is already started.</exception>
		/// <returns>The started bot management class.</returns>
		public GroupMeBot StartBot()
		{
			if (_createBotCommand.CommandExecuted)
			{
				throw new NotSupportedException("The bot cannot be started twice.");
			}

			Task<GroupMeBot> createBotTask = StartBotAsyncInternal();
			Task.WaitAll(createBotTask);
			return createBotTask.Result;
		}

		/// <summary>
		/// Starts the bot using the currently assigned bot-data asynchronous.
		/// </summary>
		/// <exception cref="NotSupportedException">Occurs if the bot is already started.</exception>
		/// <returns>The started bot management class inside a awaitable task.</returns>
		public async Task<GroupMeBot> StartBotAsync()
		{
			if (_createBotCommand.CommandExecuted)
			{
				throw new NotSupportedException("The bot cannot be started twice.");
			}

			return await StartBotAsyncInternal();
		}

		/// <summary>
		/// Defines that the current bot should contain a avatar.
		/// </summary>
		/// <param name="imageUrl">The GroupMe imaging service image url.</param>
		/// <exception cref="NotSupportedException">Occurs if the bot is already started.</exception>
		/// <returns>The extended bot-management class.</returns>
		public GroupMeBot WithAvatar(string imageUrl)
		{
			if (_createBotCommand.CommandExecuted)
			{
				throw new NotSupportedException("The bot cannot be changed after it was started.");
			}

			_createBotCommand.WithAvatar(imageUrl);
			return this;
		}

		/// <summary>
		/// Defines that the current bot should contain a callback url.
		/// </summary>
		/// <param name="callbackUrl">The callback url of the bot.</param>
		/// <exception cref="NotSupportedException">Occurs if the bot is already started.</exception>
		/// <returns>The extended bot-management class.</returns>
		public GroupMeBot WithCallback(string callbackUrl)
		{
			if (_createBotCommand.CommandExecuted)
			{
				throw new NotSupportedException("The bot cannot be changed after it was started.");
			}

			_createBotCommand.WithCallback(callbackUrl);
			return this;
		}

		internal async Task SendMessageInternal(PostBotMessageCommand command)
		{
			GroupMeApiResponse<EmptyResponse> result = await _webApiAccess.ExecuteCommand(command);
			if (result != null && result.Meta != null && result.Meta.ParsedCode != GroupMeApiResultCode.Created)
			{
				throw new GroupMeException(result.Meta);
			}
		}

		private async Task DestroyBotAsyncInternal()
		{
			var destroyBotCommand = new DestroyBotCommand(_currentBotInstance.Id);
			await _webApiAccess.ExecuteCommand(destroyBotCommand);
		}

		private async Task<GroupMeBot> StartBotAsyncInternal()
		{
			GroupMeApiResponse<BotDataResponse> bot = await _webApiAccess.ExecuteCommand(_createBotCommand);

			if (bot.Meta.ParsedCode == GroupMeApiResultCode.Created)
			{
				_currentBotInstance = bot.Response.Bot;
				return this;
			}

			throw new GroupMeException(bot.Meta);
		}
	}
}